 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * James D Miles (IBM Corp.) - bug 191783, NullPointerException in FeatureDownloader
  *******************************************************************************/

 package org.eclipse.update.core.model;


 import java.net.MalformedURLException ;
 import java.net.URL ;
 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.Iterator ;
 import java.util.List ;

 import org.eclipse.update.core.IIncludedFeatureReference;
 import org.eclipse.update.core.IncludedFeatureReference;
 import org.eclipse.update.core.VersionedIdentifier;
 import org.eclipse.update.internal.core.UpdateCore;

 /**
  * Feature model object.
  * <p>
  * This class may be instantiated or subclassed by clients. However, in most
  * cases clients should instead instantiate or subclass the provided
  * concrete implementation of this model.
  * </p>
  * <p>
  * <b>Note:</b> This class/interface is part of an interim API that is still under development and expected to
  * change significantly before reaching stability. It is being made available at this early stage to solicit feedback
  * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken
  * (repeatedly) as the API evolves.
  * </p>
  * @see org.eclipse.update.core.Feature
  * @since 2.0
  */
 public class FeatureModel extends ModelObject {

     private String featureId;
     private String featureVersion;
     private String label;
     private String localizedLabel;
     private String provider;
     private String localizedProvider;
     private String imageURLString;
     private URL imageURL;
     private String os;
     private String ws;
     private String nl;
     private String arch;
     private boolean primary = false;
     private boolean exclusive=false;
     private String primaryPluginID;
     private String application;
     private String affinity;
     private InstallHandlerEntryModel installHandler;
     private URLEntryModel description;
     private URLEntryModel copyright;
     private URLEntryModel license;
     private URLEntryModel updateSiteInfo;
     private List /*of InfoModel*/ discoverySiteInfo;
     private List /*of ImportModel*/ imports;
     private List /*of PluginEntryModel*/ pluginEntries;
     private List /*of IncludedFeatureReferenceModel */ featureIncludes;
     private List /*of NonPluginEntryModel*/ nonPluginEntries;

     // performance
 private URL bundleURL;
     private URL base;
     private boolean resolved = false;

     /**
      * Creates an uninitialized feature object.
      *
      * @since 2.0
      */
     public FeatureModel() {
         super();
     }

     /**
      * Compares 2 feature models for equality
      *
      * @param obj feature model to compare with
      * @return <code>true</code> if the two models are equal,
      * <code>false</code> otherwise
      * @since 2.0
      */
     public boolean equals(Object obj) {
         if (!(obj instanceof FeatureModel))
             return false;
         FeatureModel model = (FeatureModel) obj;

         return (featureId.toLowerCase().equals(model.getFeatureIdentifier()) && featureVersion.toLowerCase().equals(model.getFeatureVersion()));
     }

     /**
      * Returns the feature identifier as a string
      *
      * @see org.eclipse.update.core.IFeature#getVersionedIdentifier()
      * @return feature identifier
      * @since 2.0
      */
     public String getFeatureIdentifier() {
         //delayedResolve(); no delay
 return featureId;
     }

     /**
      * Returns the feature version as a string
      *
      * @see org.eclipse.update.core.IFeature#getVersionedIdentifier()
      * @return feature version
      * @since 2.0
      */
     public String getFeatureVersion() {
         //delayedResolve(); no delay
 return featureVersion;
     }

     /**
      * Retrieve the displayable label for the feature. If the model
      * object has been resolved, the label is localized.
      *
      * @return displayable label, or <code>null</code>.
      * @since 2.0
      */
     public String getLabel() {
         delayedResolve();
         if (localizedLabel != null)
             return localizedLabel;
         else
             return label;
     }

     /**
      * Retrieve the non-localized displayable label for the feature.
      *
      * @return non-localized displayable label, or <code>null</code>.
      * @since 2.0
      */
     public String getLabelNonLocalized() {
         return label;
     }

     /**
      * Retrieve the displayable label for the feature provider. If the model
      * object has been resolved, the label is localized.
      *
      * @return displayable label, or <code>null</code>.
      * @since 2.0
      */
     public String getProvider() {
         delayedResolve();
         if (localizedProvider != null)
             return localizedProvider;
         else
             return provider;
     }

     /**
      * Retrieve the non-localized displayable label for the feature provider.
      *
      * @return non-localized displayable label, or <code>null</code>.
      * @since 2.0
      */
     public String getProviderNonLocalized() {
         return provider;
     }

     /**
      * Returns the unresolved URL string for the feature image.
      *
      * @return url string, or <code>null</code>
      * @since 2.0
      */
     public String getImageURLString() {
         delayedResolve();
         return imageURLString;
     }

     /**
      * Returns the resolved URL for the image.
      *
      * @return url, or <code>null</code>
      * @since 2.0
      */
     public URL getImageURL() {
         delayedResolve();
         return imageURL;
     }

     /**
      * Get optional operating system specification as a comma-separated string.
      *
      * @return the operating system specification string, or <code>null</code>.
      * @since 2.0
      */
     public String getOS() {
         return os;
     }

     /**
      * Get optional windowing system specification as a comma-separated string.
      * @return the windowing system specification string, or <code>null</code>.
      * @since 2.0
      */
     public String getWS() {
         return ws;
     }

     /**
      * Get optional system architecture specification as a comma-separated string.
      *
      * @return the system architecture specification string, or <code>null</code>.
      * @since 2.0
      */
     public String getOSArch() {
         return arch;
     }

     /**
      * Get optional locale specification as a comma-separated string.
      *
      * @return the locale specification string, or <code>null</code>.
      * @since 2.0
      */
     public String getNL() {
         return nl;
     }

     /**
      * Indicates whether the feature can be used as a primary feature.
      *
      * @return <code>true</code> if this is a primary feature,
      * otherwise <code>false</code>
      * @since 2.0
      */
     public boolean isPrimary() {
         return primary;
     }
     
     /**
      * Indicates whether the feature must be processed alone
      * during installation and configuration. Features that
      * are not exclusive can be installed in a batch.
      *
      * @return <code>true</code> if feature requires
      * exclusive processing, <code>false</code> otherwise.
      * @since 2.1
      */
     public boolean isExclusive() {
         return exclusive;
     }

     /**
      * Returns an optional identifier for the feature application
      *
      * @return application identifier, or <code>null</code>.
      * @since 2.0
      */
     public String getApplication() {
         return application;
     }

     /**
      * Returns an optional identifier for the colocation affinity feature
      *
      * @return feature identifier, or <code>null</code>.
      * @since 2.0
      */
     public String getAffinityFeature() {
         return affinity;
     }

     /**
      * Returns and optional custom install handler entry.
      *
      * @return install handler entry, or <code>null</code> if
      * none was specified
      * @since 2.0
      */
     public InstallHandlerEntryModel getInstallHandlerModel() {
         //delayedResolve(); no delay
 return installHandler;
     }

     /**
      * Returns the feature description.
      *
      * @return feature rescription, or <code>null</code>.
      * @since 2.0
      */
     public URLEntryModel getDescriptionModel() {
         //delayedResolve(); no delay
 return description;
     }

     /**
      * Returns the copyright information for the feature.
      *
      * @return copyright information, or <code>null</code>.
      * @since 2.0
      */
     public URLEntryModel getCopyrightModel() {
         //delayedResolve(); no delay
 return copyright;
     }

     /**
      * Returns the license information for the feature.
      *
      * @return feature license, or <code>null</code>.
      * @since 2.0
      */
     public URLEntryModel getLicenseModel() {
         //delayedResolve(); no delay;
 return license;
     }

     /**
      * Returns an information entry referencing the location of the
      * feature update site.
      *
      * @return update site entry, or <code>null</code>.
      * @since 2.0
      */
     public URLEntryModel getUpdateSiteEntryModel() {
         //delayedResolve(); no delay;
 return updateSiteInfo;
     }

     /**
      * Return an array of information entries referencing locations of other
      * update sites.
      *
      * @return an array of site entries, or an empty array.
      * @since 2.0
      * @since 2.0
      */
     public URLEntryModel[] getDiscoverySiteEntryModels() {
         //delayedResolve(); no delay;
 if (discoverySiteInfo == null || discoverySiteInfo.size() == 0)
             return new URLEntryModel[0];

         return (URLEntryModel[]) discoverySiteInfo.toArray(arrayTypeFor(discoverySiteInfo));
     }

     /**
      * Return a list of plug-in dependencies for this feature.
      *
      * @return the list of required plug-in dependencies, or an empty array.
      * @since 2.0
      */
     public ImportModel[] getImportModels() {
         //delayedResolve(); no delay;
 if (imports == null || imports.size() == 0)
             return new ImportModel[0];

         return (ImportModel[]) imports.toArray(arrayTypeFor(imports));
     }

     /**
      * Returns an array of plug-in entries referenced by this feature
      *
      * @return an erray of plug-in entries, or an empty array.
      * @since 2.0
      */
     public PluginEntryModel[] getPluginEntryModels() {
         if (pluginEntries == null || pluginEntries.size() == 0)
             return new PluginEntryModel[0];

         return (PluginEntryModel[]) pluginEntries.toArray(arrayTypeFor(pluginEntries));
     }

     /**
      * Returns an array of versioned identifier referenced by this feature
      *
      * @return an array of versioned identifier, or an empty array.
      * @deprecated use getFeatureIncludeIdentifier instead.
      * @since 2.0
      */
     public VersionedIdentifier[] getFeatureIncludeVersionedIdentifier() {
         //delayedResolve(); no delay
 if (featureIncludes == null)
             return new VersionedIdentifier[0];

         //
 Iterator iter = featureIncludes.iterator();
         VersionedIdentifier[] versionIncluded = new VersionedIdentifier[featureIncludes.size()];
         int index = 0;
         while (iter.hasNext()) {
             IncludedFeatureReferenceModel model = (IncludedFeatureReferenceModel) iter.next();
             versionIncluded[index] = model.getVersionedIdentifier();
             index++;
         }
         return versionIncluded;
     }

     /**
      * Returns an array of included feature reference model referenced by this feature.
      *
      * @return an array of included feature reference model, or an empty array.
      * @since 2.0
      */
     public IIncludedFeatureReference[] getFeatureIncluded() {
         //delayedResolve(); no delay
 if (featureIncludes == null || featureIncludes.size() == 0)
             return new IIncludedFeatureReference[0];
         return (IIncludedFeatureReference[]) featureIncludes.toArray(arrayTypeFor(featureIncludes));
     }

     /**
      * Returns an array of non-plug-in entries referenced by this feature
      *
      * @return an erray of non-plug-in entries, or an empty array.
      * @since 2.0
      */
     public NonPluginEntryModel[] getNonPluginEntryModels() {
         if (nonPluginEntries == null || nonPluginEntries.size() == 0)
             return new NonPluginEntryModel[0];

         return (NonPluginEntryModel[]) nonPluginEntries.toArray(arrayTypeFor(nonPluginEntries));
     }

     /**
      * Sets the feature identifier.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param featureId feature identifier
      * @since 2.0
      */
     public void setFeatureIdentifier(String featureId) {
         assertIsWriteable();
         this.featureId = featureId;
     }

     /**
      * Sets the feature version.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param featureVersion feature version
      * @since 2.0
      */
     public void setFeatureVersion(String featureVersion) {
         assertIsWriteable();
         this.featureVersion = featureVersion;
     }

     /**
      * Sets the feature displayable label.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param label displayable label
      * @since 2.0
      */
     public void setLabel(String label) {
         assertIsWriteable();
         this.label = label;
         this.localizedLabel = null;
     }

     /**
      * Sets the feature provider displayable label.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param provider provider displayable label
      * @since 2.0
      */
     public void setProvider(String provider) {
         assertIsWriteable();
         this.provider = provider;
         this.localizedProvider = null;
     }

     /**
      * Sets the unresolved URL for the feature image.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param imageURLString unresolved URL string
      * @since 2.0
      */
     public void setImageURLString(String imageURLString) {
         assertIsWriteable();
         this.imageURLString = imageURLString;
         this.imageURL = null;
     }

     /**
      * Sets the operating system specification.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param os operating system specification as a comma-separated list
      * @since 2.0
      */
     public void setOS(String os) {
         assertIsWriteable();
         this.os = os;
     }

     /**
      * Sets the windowing system specification.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param ws windowing system specification as a comma-separated list
      * @since 2.0
      */
     public void setWS(String ws) {
         assertIsWriteable();
         this.ws = ws;
     }

     /**
      * Sets the locale specification.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param nl locale specification as a comma-separated list
      * @since 2.0
      */
     public void setNL(String nl) {
         assertIsWriteable();
         this.nl = nl;
     }

     /**
      * Sets the system architecture specification.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param arch system architecture specification as a comma-separated list
      * @since 2.0
      */
     public void setArch(String arch) {
         assertIsWriteable();
         this.arch = arch;
     }

     /**
      * Indicates whether this feature can act as a primary feature.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param primary <code>true</code> if this feature can act as primary,
      * <code>false</code> otherwise
      *
      * @since 2.0
      */
     public void setPrimary(boolean primary) {
         assertIsWriteable();
         this.primary = primary;
     }
     
     /**
      * Indicates whether this feature can act as a primary feature.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param exclusive <code>true</code> if this feature must be
      * processed independently from other features, <code>false</code>
      * if feature can be processed in a batch with other features.
      *
      * @since 2.1
      */
     public void setExclusive(boolean exclusive) {
         assertIsWriteable();
         this.exclusive = exclusive;
     }

     /**
      * Sets the feature application identifier.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param application feature application identifier
      * @since 2.0
      */
     public void setApplication(String application) {
         assertIsWriteable();
         this.application = application;
     }

     /**
      * Sets the identifier of the Feature this feature should be
      * installed with.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param affinity the identifier of the Feature
      * @since 2.0
      */
     public void setAffinityFeature(String affinity) {
         assertIsWriteable();
         this.affinity = affinity;
     }

     /**
      * Sets the custom install handler for the feature.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param installHandler install handler entry
      * @since 2.0
      */
     public void setInstallHandlerModel(InstallHandlerEntryModel installHandler) {
         assertIsWriteable();
         this.installHandler = installHandler;
     }

     /**
      * Sets the feature description information.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param description feature description information
      * @since 2.0
      */
     public void setDescriptionModel(URLEntryModel description) {
         assertIsWriteable();
         this.description = description;
     }

     /**
      * Sets the feature copyright information.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param copyright feature copyright information
      * @since 2.0
      */
     public void setCopyrightModel(URLEntryModel copyright) {
         assertIsWriteable();
         this.copyright = copyright;
     }

     /**
      * Sets the feature license information.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param license feature license information
      * @since 2.0
      */
     public void setLicenseModel(URLEntryModel license) {
         assertIsWriteable();
         this.license = license;
     }

     /**
      * Sets the feature update site reference.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param updateSiteInfo feature update site reference
      * @since 2.0
      */
     public void setUpdateSiteEntryModel(URLEntryModel updateSiteInfo) {
         assertIsWriteable();
         this.updateSiteInfo = updateSiteInfo;
     }

     /**
      * Sets additional update site references.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param discoverySiteInfo additional update site references
      * @since 2.0
      */
     public void setDiscoverySiteEntryModels(URLEntryModel[] discoverySiteInfo) {
         assertIsWriteable();
         if (discoverySiteInfo == null)
             this.discoverySiteInfo = null;
         else
             this.discoverySiteInfo = new ArrayList (Arrays.asList(discoverySiteInfo));
     }

     /**
      * Sets the feature plug-in dependency information.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param imports feature plug-in dependency information
      * @since 2.0
      */
     public void setImportModels(ImportModel[] imports) {
         assertIsWriteable();
         if (imports == null)
             this.imports = null;
         else
             this.imports = new ArrayList (Arrays.asList(imports));
     }

     /**
      * Sets the feature plug-in references.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param pluginEntries feature plug-in references
      * @since 2.0
      */
     public void setPluginEntryModels(PluginEntryModel[] pluginEntries) {
         assertIsWriteable();
         if (pluginEntries == null)
             this.pluginEntries = null;
         else
             this.pluginEntries = new ArrayList (Arrays.asList(pluginEntries));
     }

     /**
      * Sets the feature non-plug-in data references.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param nonPluginEntries feature non-plug-in data references
      * @since 2.0
      */
     public void setNonPluginEntryModels(NonPluginEntryModel[] nonPluginEntries) {
         assertIsWriteable();
         if (nonPluginEntries == null)
             this.nonPluginEntries = null;
         else
             this.nonPluginEntries = new ArrayList (Arrays.asList(nonPluginEntries));
     }

     /**
      * Adds an additional update site reference.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param discoverySiteInfo update site reference
      * @since 2.0
      */
     public void addDiscoverySiteEntryModel(URLEntryModel discoverySiteInfo) {
         assertIsWriteable();
         if (this.discoverySiteInfo == null)
             this.discoverySiteInfo = new ArrayList ();
         if (!this.discoverySiteInfo.contains(discoverySiteInfo))
             this.discoverySiteInfo.add(discoverySiteInfo);
     }

     /**
      * Adds a plug-in dependency entry.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param importEntry plug-in dependency entry
      * @since 2.0
      */
     public void addImportModel(ImportModel importEntry) {
         assertIsWriteable();
         if (this.imports == null)
             this.imports = new ArrayList ();
         if (!this.imports.contains(importEntry))
             this.imports.add(importEntry);
     }

     /**
      * Adds a plug-in reference.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param pluginEntry plug-in reference
      * @since 2.0
      */
     public void addPluginEntryModel(PluginEntryModel pluginEntry) {
         assertIsWriteable();
         if (this.pluginEntries == null)
             this.pluginEntries = new ArrayList ();
         //PERF: no ListContains()
 //if (!this.pluginEntries.contains(pluginEntry))
 this.pluginEntries.add(pluginEntry);
     }

     /**
      * Adds a feature identifier.
      * Throws a runtime exception if this object is marked read-only.
      * @param include the included feature
      * @since 2.1
      */
     public void addIncludedFeatureReferenceModel(IncludedFeatureReferenceModel include) {
         assertIsWriteable();
         if (this.featureIncludes == null)
             this.featureIncludes = new ArrayList ();
         //PERF: no ListContains()
 //if (!this.featureIncludes.contains(include))
 this.featureIncludes.add(new IncludedFeatureReference(include));
     }

     /**
      * Adds a non-plug-in data reference.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param nonPluginEntry non-plug-in data reference
      * @since 2.0
      */
     public void addNonPluginEntryModel(NonPluginEntryModel nonPluginEntry) {
         assertIsWriteable();
         if (this.nonPluginEntries == null)
             this.nonPluginEntries = new ArrayList ();
         //PERF: no ListContains()
 //if (!this.nonPluginEntries.contains(nonPluginEntry))
 this.nonPluginEntries.add(nonPluginEntry);
     }

     /**
      * Removes an update site reference.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param discoverySiteInfo update site reference
      * @since 2.0
      */
     public void removeDiscoverySiteEntryModel(URLEntryModel discoverySiteInfo) {
         assertIsWriteable();
         if (this.discoverySiteInfo != null)
             this.discoverySiteInfo.remove(discoverySiteInfo);
     }

     /**
      * Removes a plug-in dependency entry.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param importEntry plug-in dependency entry
      * @since 2.0
      */
     public void removeImportModel(ImportModel importEntry) {
         assertIsWriteable();
         if (this.imports != null)
             this.imports.remove(importEntry);
     }

     /**
      * Removes a plug-in reference.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param pluginEntry plug-in reference
      * @since 2.0
      */
     public void removePluginEntryModel(PluginEntryModel pluginEntry) {
         assertIsWriteable();
         if (this.pluginEntries != null)
             this.pluginEntries.remove(pluginEntry);
     }

     /**
      * Removes a non-plug-in data reference.
      * Throws a runtime exception if this object is marked read-only.
      *
      * @param nonPluginEntry non-plug-in data reference
      * @since 2.0
      */
     public void removeNonPluginEntryModel(NonPluginEntryModel nonPluginEntry) {
         assertIsWriteable();
         if (this.nonPluginEntries != null)
             this.nonPluginEntries.remove(nonPluginEntry);
     }

     /**
      * Marks the model object as read-only.
      *
      * @since 2.0
      */
     public void markReadOnly() {
         super.markReadOnly();
         markReferenceReadOnly(getDescriptionModel());
         markReferenceReadOnly(getCopyrightModel());
         markReferenceReadOnly(getLicenseModel());
         markReferenceReadOnly(getUpdateSiteEntryModel());
         markListReferenceReadOnly(getDiscoverySiteEntryModels());
         markListReferenceReadOnly(getImportModels());
         markListReferenceReadOnly(getPluginEntryModels());
         markListReferenceReadOnly(getNonPluginEntryModels());
     }

     /**
      * Resolve the model object.
      * Any URL strings in the model are resolved relative to the
      * base URL argument. Any translatable strings in the model that are
      * specified as translation keys are localized using the supplied
      * resource bundle.
      *
      * @param base URL
      * @param bundleURL resource bundle url
      * @exception MalformedURLException
      * @since 2.0
      */
     public void resolve(URL base,URL bundleURL) throws MalformedURLException {
         this.bundleURL = bundleURL;
         this.base = base;

         // plugin entry and nonpluginentry are optimized too
 resolveListReference(getPluginEntryModels(), base, bundleURL);
         resolveListReference(getNonPluginEntryModels(), base, bundleURL);
         
         //URLSiteModel are optimized
 resolveReference(getDescriptionModel(),base, bundleURL);
         resolveReference(getCopyrightModel(),base, bundleURL);
         resolveReference(getLicenseModel(),base, bundleURL);
         resolveReference(getUpdateSiteEntryModel(),base, bundleURL);
         resolveListReference(getDiscoverySiteEntryModels(),base, bundleURL);
         
         // Import Models are optimized
 resolveListReference(getImportModels(),base, bundleURL);
     }

     private void delayedResolve() {

         // PERF: delay resolution
 if (resolved)
             return;

         // resolve local elements
 localizedLabel = resolveNLString(bundleURL, label);
         localizedProvider = resolveNLString(bundleURL, provider);
         try {
             imageURL = resolveURL(base,bundleURL, imageURLString);
             resolved = true;
         } catch (MalformedURLException e){
             UpdateCore.warn("",e); //$NON-NLS-1$
 }
     }

     /**
      * Method setPrimaryPlugin.
      * @param plugin
      */
     public void setPrimaryPluginID(String plugin) {
         if (primary && primaryPluginID == null) {
             primaryPluginID = featureId;
         }
         primaryPluginID = plugin;
     }
     /**
      * Returns the primaryPluginID.
      * @return String
      */
     public String getPrimaryPluginID() {
         return primaryPluginID;
     }

     /**
      * Returns <code>true</code> if this feature is patching another feature,
      * <code>false</code> otherwise
      * @return boolean
      */
     public boolean isPatch() {
         ImportModel[] imports = getImportModels();

         for (int i = 0; i < imports.length; i++) {
             if (imports[i].isPatch())
                 return true;
         }
         return false;
     }
 }
